package genetic;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.tree.TreeNode;

public class GeneticProgram implements Comparable<Object>, Serializable, Cloneable {

	private static Random rd = new Random();

	public static GeneticProgram crossover(GeneticProgram parent1, GeneticProgram parent2) {
		// 1. determine max depth of both trees
		int maxDepth1 = parent1.findMaxTreeDepth();
		int maxDepth2 = parent2.findMaxTreeDepth();
		
		// 2. clone both parents
		GeneticProgram child = parent1.clone();
		GeneticProgram genpool = parent2.clone();
		
		// 3. choose random crossover point for each tree
		//    2 sigma der Gaussverteilung soll = maxdepth sein
		Random rd = new Random();
		
		int maxWay1 = rd.nextInt(maxDepth1);
		int maxWay2 = rd.nextInt(maxDepth2);

		if (maxWay1 == 0)
			maxWay1 = 1;
		if (maxWay2 == 0)
			maxWay2 = 1;
		
		GPTreeNode crossoverNode1 = child.chooseCrossoverNode(maxWay1);
		GPTreeNode crossoverNode2 = genpool.chooseCrossoverNode(maxWay2);

		// --- DEBUG CODE ---
		//System.out.println("Crossover Summary:  maxDepth=("+maxDepth1+","+maxDepth2+")  maxWay=("+
		//		maxWay1+","+maxWay2+")  nodes=("+crossoverNode1+","+crossoverNode2+")");
		// --- DEBUG CODE ---
		
		// 4. replace subtree of first parent
		((GPTreeNode)(crossoverNode1.getParent())).replaceChild(crossoverNode1, crossoverNode2);
		
		// 5. remove reference of old subtree of first parent
		//    (ensure gc still works)
		//TODO moeglicherweise auch nicht
		
		return child;
	}

	private double error = 0.0;
	private GPTreeNode root;

	private ArrayList<GPTreeNode> functionSet;
	private ArrayList<GPTreeNode> variableSet;
	private ArrayList<GPTreeNode> constantSet;

	private int sizeOfFunctionSet = 0;
	private int sizeOfVariableSet = 0;
	private int sizeOfConstantSet = 0;
	
	public GeneticProgram(ArrayList<GPTreeNode> functionSet,
			ArrayList<GPTreeNode> variableSet, ArrayList<GPTreeNode> constantSet) {
		this.functionSet = functionSet;
		this.variableSet = variableSet;
		this.constantSet = constantSet;
		
		sizeOfFunctionSet = functionSet.size();
		sizeOfVariableSet = variableSet.size();
		sizeOfConstantSet = constantSet.size();

		// init root node with random element from function set
		int a = rd.nextInt(sizeOfFunctionSet);
		root = functionSet.get(a).clone();
	}

	private GPTreeNode chooseCrossoverNode(int maxWayPoints) {
		GPTreeNode current = this.root;
		int wayPoint = 0;
		Random rd = new Random();
		
		while (current.isLeaf() == false && wayPoint < maxWayPoints) {
			int next = rd.nextInt(current.getChildCount());
			current = (GPTreeNode)current.getChildAt(next);
			
			wayPoint++;
		}
		
		return current;
	}

	public GeneticProgram clone() {
		GeneticProgram gp = null;
		try {
			gp = (GeneticProgram)super.clone();
			gp.root = this.root.clone();
			gp.functionSet = this.functionSet;
			gp.variableSet = this.variableSet;
			gp.constantSet = this.constantSet;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return gp;
	}
	
	public int compareTo(Object o) {
		if (this.error > ((GeneticProgram) o).error) {
			return 1;
		} else {
			return 0;
		}
	}
	
	public void createRandom(int maxDepth, int method) {
		if (maxDepth < 1)
			createRandomSubtree(1, method, root);
		else
			createRandomSubtree(maxDepth, method, root);
	}
	
	private void createRandomSubtree(int maxDepth, int method, GPTreeNode node) {
		if ((maxDepth == 1)
				|| (method == 1 && rd.nextDouble() < (double) (sizeOfVariableSet+sizeOfConstantSet)
						/ (sizeOfVariableSet+sizeOfConstantSet+sizeOfFunctionSet))) {
			// this is the last function node before ground level
			// --> choose random element from terminal set for each child
			for (int i = 0; i < GPTreeNode.convertArity(node.getArity()); i++) {
				if (rd.nextDouble() < 0.5) {
					// choose from variable set
					int a = rd.nextInt(sizeOfVariableSet);
					GPTreeNode child = variableSet.get(a).clone();
					child.setParent(node);
					node.addChild(child);
				} else {
					// choose from constant set
					int a = rd.nextInt(sizeOfConstantSet);
					GPTreeNode child = constantSet.get(a).clone();
					child.setParent(node);
					node.addChild(child);
				}
			}
		} else {
			// choose random element from function set
			for (int i = 0; i < GPTreeNode.convertArity(node.getArity()); i++) {
				int a = rd.nextInt(functionSet.size());
				GPTreeNode child = functionSet.get(a).clone();
				child.setParent(node);
				node.addChild(child);
				createRandomSubtree(maxDepth - 1, method, child);
			}
		}
	}
	
	public double evaluate(DataSet ds) {
		return root.getValue(ds);
	}
	
	public double evaluateWithErrorCalculation(DataSet ds, double targetValue) {
		double value = evaluate(ds);
		error += (value - targetValue) * (value - targetValue);
		
		return value;
	}
	
	public int findMaxTreeDepth() {
		return findSubtreeDepth(root, 0);
	}

	private int findSubtreeDepth(GPTreeNode node, int level) {
		int max = level;
		if (node.getChildCount() > 0) {
			for (int i=0; i<node.getChildCount(); i++) {
				if (findSubtreeDepth((GPTreeNode)node.getChildAt(i), level+1) > max) {
					max = findSubtreeDepth((GPTreeNode)node.getChildAt(i), level+1);
				}
			}
		}
		return max;
	}
	
	public double getError() {
		return error;
	}
	
	public TreeNode getRoot() {
		return root;
	}

	public void resetError() {
		error = 0.0;
	}

	public String statistics() {
		return "ID="+this+"  err="+error;
	}

	public void test_killOneSubtree() {
		// test routine
		root.test_killOneSubtree();
	}
}
